﻿using ezCloud.Api.Common;
using ezCloud.Api.Models;
using ezCloud.Data;
using Microsoft.AspNet.Identity;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Linq;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;

namespace ezCloud.Api.Controllers
{
    [ApiAuthorize]
    [RoutePrefix("api/Reservation")]
    public class ReservationController : ezController
    {
        private MemoryCacher memCache = new MemoryCacher();
        [HttpGet]
        [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
        [Route("Detail")]
        public IHttpActionResult ReservationRoomDetail(DateTime date, int? reservationRoomId)
        {
            return Ok();
        }


        [HttpGet]
        [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
        [Route("GetRoomChargesList")]
        public async Task<IHttpActionResult> GetRoomChargesList(int? reservationRoomId)
        {
            var user = UserUtitily.getUserProfile();
            var currentHotel = context.Hotels.Where(ht => ht.HotelId == user.HotelId).FirstOrDefault();
            if (currentHotel != null)
            {
                var currentRR = currentHotel.ReservationRoomsList.FirstOrDefault(rr => rr.ReservationRoomId == reservationRoomId);
                var roomCharges = currentRR.RoomChargesList.ToList();
                return Ok( new
                {
                    roomCharges = roomCharges
                });
            }
            return Ok();
        }
    
        [HttpGet]
        [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
        [Route("ProcessReservation")]
        public async Task<IHttpActionResult> ProcessReservation(DateTime date, int? reservationRoomId)
        {
            var user = UserUtitily.getUserProfile();
            var currentHotel = context.Hotels.Where(ht => ht.HotelId == user.HotelId).FirstOrDefault();
            if (currentHotel != null)
            {
                var hotelIndexData = GetAllHotelIndexAndStaticData(currentHotel);
                if (reservationRoomId != null)
                {
                    var currentRR = currentHotel.ReservationRoomsList.FirstOrDefault(rr => rr.ReservationRoomId == reservationRoomId);
                    if (currentRR != null)
                    {
                        var rooms = currentRR.Rooms;
                        var roomTypes = currentRR.RoomTypes;
                        var roomPrices = currentRR.RoomPrices;
                        var payments = currentRR.PaymentsList.ToList();
                        var roomExtraServices = currentRR.RoomExtraServicesList.ToList();
                        var roomExtraServiceItems = currentRR.RoomExtraServiceItemsList.ToList();
                        var reservation = currentRR.Reservations;
                        var roomCharges = currentRR.RoomChargesList.ToList();
                        var roomBreakfast = currentRR.RoomBreakfastList.ToList();
                        var roomRemarks = currentRR.RoomRemarksList.ToList();
                        var travellers = currentRR.Travellers;
                        var company = currentRR.Reservations.Company;
                        var source = currentRR.Reservations.Source;
                        var market = currentRR.Reservations.Market;
                        var sharer = currentRR.ReservationTravellersList;
                        var sharerList = new List<SharerModel>();
                        foreach (var sha in sharer)
                        {
                            var temp = new SharerModel();
                            temp.customer = currentRR.TravellersList.Where(s => s.TravellerId == sha.TravellerId).FirstOrDefault();
                            temp.reservationRoomId = reservationRoomId.Value;

                            var travellerExtra = sha.ReservationTravellerExtraInformationList.FirstOrDefault();
                            if (travellerExtra == null)
                            {
                                travellerExtra = new ReservationTravellerExtraInformation();
                                travellerExtra.HotelId = user.HotelId;
                                travellerExtra.ReservationTravellerId = sha.ReservationTravellerId;
                                travellerExtra.IsChild = false;
                                context.ReservationTravellerExtraInformation.InsertOnSubmit(travellerExtra);
                                context.SubmitChanges();
                            }
                            temp.travellerExtraInfo = travellerExtra;
                            sharerList.Add(temp);
                        }
                        var changeReservationStatus = currentRR.ChangeReservationStatusList;
                        var pastCheckOut = currentRR.PastCheckOutList.FirstOrDefault();
                        var applyPastCheckOut = pastCheckOut != null ? true : false;

                        // Calculate Room Price
                        PlanListDateRange CalculateRoomPrice = null;
                        CalculateRoomPrice RoomPrice = null;
                        if (currentRR.BookingStatus == "CHECKIN" || currentRR.BookingStatus == "BOOKED")
                        {
                            RoomPrice = new CalculateRoomPrice()
                            {
                                roomId = rooms != null ? rooms.RoomId : 0,
                                arrivalDate = currentRR.ArrivalDate.Value,
                                departureDate = DateTime.UtcNow,
                                adults = currentRR.Adults,
                                children = currentRR.Child,
                                RoomPriceId = roomPrices.RoomPriceId,
                                FOC = currentRR.Foc,
                                DiscountPercentage = (float)currentRR.DiscountPercentage,
                                DiscountFlat = currentRR.DiscountFlat,
                                Price = currentRR.Price,
                                reservationRoomId = currentRR.ReservationRoomId,
                                roomTypeId = (int)currentRR.RoomTypeId
                            };
                            if (applyPastCheckOut)
                            {
                                RoomPrice.departureDate = currentRR.DepartureDate.Value;
                            }
                            if (currentRR.BookingStatus == "BOOKED" || currentRR.BookingStatus == "NOSHOW")
                            {
                                RoomPrice.departureDate = currentRR.DepartureDate.Value;
                            }
                            var CalculateRoomPriceTmp = await RoomPriceUtilities.CalculateRoomPrice(RoomPrice);
                            CalculateRoomPrice = CalculateRoomPriceTmp;
                        }

                        return Ok(new
                        {
                            HotelIndexData = hotelIndexData,
                            //HotelIndexData = new { },
                            ReservationRoomInfo = currentRR,
                            Sharers = sharerList,
                            CalculateRoomPrice
                        });
                    }
                    else
                    {
                        return Ok(new
                        {
                            HotelIndexData = hotelIndexData
                        });
                    }
                }
                else
                {
                    //if (roomId != null)
                    //{
                    //    var hotelIndexDataDeepCopy = hotelIndexData.DeepCopyByExpressionTree();
                    //    hotelIndexDataDeepCopy.rooms = currentHotel.RoomsList.ToList().Where(rm => rm.IsDeleted == false && rm.IsHidden == false);
                    //    return Ok(new
                    //    {
                    //        HotelIndexData = hotelIndexDataDeepCopy
                    //    });
                    //}
                    //else
                    //{
                        return Ok(new
                        {
                            HotelIndexData = hotelIndexData
                        });
                    //}
                    
                }
            }
            else
            {
                return BadRequest("HOTEL_DOES_NOT_EXIST");
            }
        }

        [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
        [Route("AvailableRooms")]
        public IHttpActionResult AvailableRooms(AvailableRoomModel model)
        {
            var user = UserUtitily.getUserProfile();
            var currentHotel = context.Hotels.Where(ht => ht.HotelId == user.HotelId).FirstOrDefault();
            if (currentHotel != null)
            {
                //IEnumerable<Rooms> availableRooms = Enumerable.Empty<Rooms>();
                var intersectRRs = new HashSet<int>(currentHotel.ReservationRoomsList.Where(rr => rr.BookingStatus != "CHECKOUT" && rr.BookingStatus != "CANCELLED" && rr.RoomId != null && rr.RoomTypeId == model.roomTypeId &&(( model.arrivalDate <= rr.DepartureDate && rr.ArrivalDate <= model.departureDate))).Select(rr => (int)rr.RoomId));
                //var intersectRRs = currentHotel.ReservationRoomsList.Where(rr => rr.BookingStatus != "CHECKOUT" && rr.BookingStatus != "CANCELLED" && rr.RoomId != null && ((model.arrivalDate <= rr.DepartureDate && rr.ArrivalDate <= model.departureDate))).ToList();
                var availableRooms = currentHotel.RoomsList.Where(rm => rm.IsDeleted == false && rm.IsHidden == false && rm.RoomTypeId == model.roomTypeId && !intersectRRs.Contains(rm.RoomId));
                return Ok(availableRooms);
                //return Ok();
            }
            else
            {
                return BadRequest("HOTEL_DOES_NOT_EXIST");
            }
            
        }

        [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
        [Route("AmendStayReservations")]
        public IHttpActionResult AmendStayReservations(AmendStayReservationModel model)
        {
            var user = UserUtitily.getUserProfile();
            var currentHotel = context.Hotels.Where(ht => ht.HotelId == user.HotelId).FirstOrDefault();
            if (currentHotel != null)
            {
                var currentRR = currentHotel.ReservationRoomsList.Where(rr => rr.ReservationRoomId == model.ReservationRoomId).FirstOrDefault();
                if (currentRR != null)
                {
                    if (currentRR.RoomId != null)
                    {

                        
                        var relatedRRs = currentHotel.ReservationRoomsList.Where(rr => rr.RoomId != null && rr.RoomId == currentRR.RoomId && rr.BookingStatus != "CHECKOUT" && rr.BookingStatus != "CANCELLED").Select(rr => new AmendStayReservationModel()
                        {
                            ReservationNumber = (int)rr.Reservations.ReservationNumber,
                            TravellerName = rr.Travellers.Fullname,
                            ArrivalDate = rr.ArrivalDate,
                            DepartureDate = rr.DepartureDate,
                            ReservationRoomId = rr.ReservationRoomId
                        });
                        var currentRRTemp = relatedRRs.FirstOrDefault(rr => rr.ReservationRoomId == currentRR.ReservationRoomId);
                        currentRRTemp.ArrivalDate = model.ArrivalDate;
                        currentRRTemp.DepartureDate = model.DepartureDate;
                        relatedRRs = relatedRRs.OrderBy(rr => rr.ArrivalDate);
                        //var next = GetNext<AmendStayReservationModel>(relatedRRs, currentRRTemp);
                        //var pre = GetPrevious<AmendStayReservationModel>(relatedRRs, currentRRTemp);
                        return Ok(new
                        {
                            NextReservation = relatedRRs.FirstOrDefault(rr => rr.ArrivalDate > currentRRTemp.ArrivalDate),
                            PreviousReservation = relatedRRs.LastOrDefault(rr => rr.ArrivalDate < currentRRTemp.ArrivalDate),
                            //PreviousReservation = relatedRRs.FirstOrDefault(rr => rr.ArrivalDate < currentRRTemp.ArrivalDate),
                        });
                    }
                    else
                    {
                        return Ok();
                    }
                    
                }
                else
                {
                    return BadRequest("HOTEL_DOES_NOT_EXIST");
                }
            }
            else
            {
                return BadRequest("HOTEL_DOES_NOT_EXIST");
            }

        }

        private static T GetNext<T>(IEnumerable<T> list, T current)
        {
            try
            {
                return list.SkipWhile(x => !x.Equals(current)).Skip(1).First();
            }
            catch
            {
                return default(T);
            }
        }

        private static T GetPrevious<T>(IEnumerable<T> list, T current)
        {
            try
            {
                return list.TakeWhile(x => !x.Equals(current)).Last();
            }
            catch
            {
                return default(T);
            }
        }

        public static T DeepCopy<T>(T other)
        {
            //using (MemoryStream ms = new MemoryStream())
            //{
            //    BinaryFormatter formatter = new BinaryFormatter();
            //    formatter.Serialize(ms, other);
            //    ms.Position = 0;
            //    return (T)formatter.Deserialize(ms);
            //}
            BinaryFormatter formatter = new BinaryFormatter();
            MemoryStream stream = new MemoryStream();
            formatter.Serialize(stream, other);
            stream.Seek(0, SeekOrigin.Begin);
            T result = (T)formatter.Deserialize(stream);
            stream.Close();
            return result;
        }
        public object GetAllHotelIndexAndStaticData(Hotels currentHotel)
        {
            var cachedData = memCache.GetValue("ALL_HOTEL_INDEX_AND_STATIC_DATA" + currentHotel.HotelId);
            if (cachedData != null)
            {
                return (AllHotelIndexAndStaticData)cachedData;
            }
            else
            {
                var currencies = currentHotel.MoneyList;
                var defaultCurrency = currencies.Where(cur => cur.MoneyId == currentHotel.DefaultMoneyId);
                var staffListTemp = context.Staffs.Where(s => s.HotelId == currentHotel.HotelId).ToList();
                var userOwner = currentHotel.HotelOwnerAspNetUsers;
                staffListTemp.Add(
                    new Staffs()
                    {
                        Email = userOwner.Email,
                        UserId = userOwner.Id
                    });
                var result = new 
                {
                    rooms =  currentHotel.RoomsList.Where(rm => rm.IsDeleted == false && rm.IsHidden == false),
                    roomTypes = currentHotel.RoomTypesList.Where(rt => rt.IsDeleted == false && rt.IsHidden == false),
                    planList = currentHotel.RoomPricesList.Where(rp => rp.IsActive == true),
                    staffList = staffListTemp,
                    statusColors = currentHotel.StatusColorsList,
                    paymentMethods = context.PaymentMethods.Select(p => new { PaymentMethodId = p.PaymentMethodId, PaymentMethodName = p.PaymentMethodName, PaymentMethodDescription = p.PaymentMethodDescription }).ToList(),
                    remarkEvents = context.RemarkEvents.ToList(),
                    sourceList = currentHotel.SourceList,
                    companyList = currentHotel.CompanyList,
                    marketList = currentHotel.MarketList,
                    currenciesISO = context.Currencies.ToList(),
                    currencies = currentHotel.MoneyList,
                    defaultCurrency = new
                    {
                        MoneyId = defaultCurrency.First().MoneyId,
                        MoneyName = defaultCurrency.First().MoneyName,
                        CurrencyInfo = defaultCurrency.First().Currencies
                    },
                    extraService = new
                    {
                        ExtraServiceTypes = currentHotel.ExtraServiceTypesList,
                        ExtraServiceCategories = currentHotel.ExtraServiceCategoriesList.Where(cat => cat.IsHidden == false),
                        //ExtraServiceItems = currentHotel.ExtraServiceItemsList.Where(item => item.IsHidden == false && item.ExtraServiceCategories.IsHidden == false)
                        ExtraServiceItems = currentHotel.ExtraServiceItemsList//.Where(item => item.IsHidden == false)
                    },
                    //countries = context.Countries,
                    hotelInvoices = new
                    {
                        hotelFormRoomInvoice = currentHotel.HotelFormList.Where(frm => frm.FormType == "RoomInvoice"),
                        hotelFormRoomReceipt = currentHotel.HotelFormList.Where(frm => frm.FormType == "Receipt"),
                    }
                };
                memCache.AddByHotelId(currentHotel.HotelId, "ALL_HOTEL_INDEX_AND_STATIC_DATA_" + currentHotel.HotelId, result, DateTimeOffset.UtcNow.AddDays(1));
                return result;
            }

        }

    }
    public static class ObjectCloner
    {
        public static T Clone<T>(object obj, bool deep = false) where T : new()
        {
            if (!(obj is T))
            {
                throw new Exception("Cloning object must match output type");
            }

            return (T)Clone(obj, deep);
        }

        public static object Clone(object obj, bool deep)
        {
            if (obj == null)
            {
                return null;
            }

            Type objType = obj.GetType();

            if (objType.IsPrimitive || objType == typeof(string) || objType.GetConstructors().FirstOrDefault(x => x.GetParameters().Length == 0) == null)
            {
                return obj;
            }

            List<PropertyInfo> properties = objType.GetProperties().ToList();
            if (deep)
            {
                properties.AddRange(objType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic));
            }

            object newObj = Activator.CreateInstance(objType);

            foreach (var prop in properties)
            {
                if (prop.GetSetMethod() != null)
                {
                    var proceed = true;
                    if (obj is IList)
                    {
                        var listType = obj.GetType().GetProperty("Item").PropertyType;
                        if (prop.PropertyType == listType)
                        {
                            proceed = false;
                            foreach (var item in obj as IList)
                            {
                                object clone = Clone(item, deep);
                                (newObj as IList).Add(clone);
                            }
                        }
                    }

                    if (proceed)
                    {
                        object propValue = prop.GetValue(obj, null);
                        object clone = Clone(propValue, deep);
                        prop.SetValue(newObj, clone, null);
                    }
                }
            }

            return newObj;
        }
    }

    public class AvailableRoomModel
    {
        public int roomTypeId { get; set; }
        public DateTime arrivalDate { get; set; }
        public DateTime departureDate { get; set; }
    }

    public class AmendStayReservationModel
    {
        public int ReservationRoomId { get; set; }
        public DateTime? ArrivalDate { get; set; }
        public DateTime? DepartureDate { get; set; }
        public string TravellerName { get; set; }
        public int ReservationNumber { get; set; }
    }
    public class AllHotelIndexAndStaticData
    {
        public IEnumerable<Rooms> rooms { get; set; }
        public IEnumerable<RoomTypes> roomTypes { get; set; }
        public IEnumerable<RoomPrices> planList { get; set; }
        public IEnumerable<Staffs> staffList { get; set; }
        public IEnumerable<StatusColors> statusColors { get; set; }
        public IEnumerable<PaymentMethods> paymentMethods { get; set; }
        public IEnumerable<RemarkEvents> remarkEvents { get; set; }
        public IEnumerable<Source> sourceList { get; set; }
        public IEnumerable<Company> companyList { get; set; }
        public IEnumerable<Market> marketList { get; set; }
        public IEnumerable<Currencies> currenciesISO { get; set; }
        public EntitySet<Money> currencies { get; set; }
        public object defaultCurrency { get; set; }
        public object extraService { get; set; }
        public IEnumerable<Countries> countries { get; set; }
        public object hotelInvoices { get; set; }

        public AllHotelIndexAndStaticData DeepCopy()
        {
            return this.DeepCopyByExpressionTree();
        }

    }

    public class SharerModel
    {
        public Travellers customer { get; set; }
        public int reservationRoomId { get; set; }
        public ReservationTravellerExtraInformation travellerExtraInfo { get; set; }
    }
}